package model

import (
	"errors"
	"fmt"
	"github.com/jinzhu/gorm"
)

var (
	ErrRoleNotFound = errors.New("无法找到角色信息，请确认角色是否存在")
)

type (
	Role struct {
		Model
		ParentID  uint              `json:"parent_id" gorm:"column:parent_id;not null;default:0"`
		Name      string            `json:"name" gorm:"column:name;size:255;default:'';not null;"`
		Slug      string            `json:"slug" gorm:"column:slug;size:255;default:'';not null;"`
		RoleMenus RoleMenuRelations `json:"role_menus" gorm:"-"`
		Children  []*Role           `json:"children" gorm:"-"`
	}
)

func IsSlugExist(id, slug string) bool {
	var check map[string]interface{}
	if id == "" {
		db.Where("slug = ?", slug).First(&check)
		return check != nil
	}
	db.Where("slug = ? and id != ?", slug, id).First(&check)
	return check != nil
}

func GetAllRoles() ([]*Role, error) {
	roles := make([]*Role, 0)
	if err := db.Model(&Role{}).Find(&roles).Error; err != nil {
		return nil, err
	}
	return roles, nil
}

func GetChildrenWithParentID(parentID int) ([]*Role, error) {
	children := make([]*Role, 0)
	if err := db.Model(&Role{}).Where("parent_id = ?", parentID).Find(&children).Error; err != nil {
		return nil, err
	}
	return children, nil
}

func GetRoles(index, size int) ([]*Role, int, error) {
	roles := make([]*Role, 0)
	total := 0
	if err := db.Model(&Role{}).Count(&total).Error; err != nil {
		return nil, -1, err
	}

	if err := db.Model(&Role{}).Offset((index - 1) * size).Limit(size).Where("parent_id = 0").Find(&roles).Error; err != nil {
		return nil, -1, err
	}

	for _, role := range roles {
		if role.ParentID == 0 {
			children, err := GetChildrenWithParentID(int(role.ID))
			if err != nil {
				return nil, -1, err
			}
			role.Children = children
		}
	}

	return roles, total, nil
}

func AddRole(role *Role) error {
	if IsSlugExist("", role.Slug) {
		return fmt.Errorf("标志已存在")
	}

	tx := db.Begin()

	for _, rm := range role.RoleMenus {
		if err := AddRoleMenu(rm, tx); err != nil {
			tx.Rollback()
			return err
		}
	}

	if err := tx.Create(role).Error; err != nil {
		return fmt.Errorf("添加角色出现错误, %w", err)
	}

	tx.Commit()

	return nil
}

func GetRoleWithID(id int) (*Role, error) {
	result := new(Role)
	if err := db.Where("id = ?", id).First(result).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, ErrRoleNotFound
		}
		return nil, err
	}
	roleMenus, err := GetRoleMenuRelationsWithRoleID(result.ID)
	if err != nil {
		return nil, err
	}
	result.RoleMenus = roleMenus
	return result, nil
}

func GetRoleWithNameAndSlug(name, slug string) (*Role, error) {
	result := new(Role)
	if err := db.Where("name = ? and slug = ?", name, slug).First(result).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, ErrRoleNotFound
		} else {
			return nil, err
		}
	}

	roleMenus, err := GetRoleMenuRelationsWithRoleID(result.ID)
	if err != nil {
		return nil, err
	}
	result.RoleMenus = roleMenus
	return result, nil
}

func GetRoleWithUserIDWithoutRoleMenuRelations(userID int) (*Role, error) {
	result := new(Role)

	userRole, err := GetUserRoleWithUserID(userID)
	if err != nil {
		return nil, err
	}

	if err := db.Model(result).Where("id = ?", userRole.RoleID).Find(result).Error; err != nil {
		if errors.Is(err, gorm.ErrRecordNotFound) {
			return nil, ErrRoleNotFound
		} else {
			return nil, err
		}
	}

	return result, nil
}

func DeleteRole(id int) error {
	role, err := GetRoleWithID(id)
	if err != nil {
		return err
	}

	rel, err := GetUserRoleRelationsWithRoleID(id)
	if len(rel) > 0 {
		return fmt.Errorf("无法删除角色[%s], 一个或多个用户属于此角色", role.Name)
	}

	tx := db.Begin()

	if err := DeleteRoleMenuByRoleID(id, tx); err != nil {
		tx.Rollback()
		return err
	}

	if err := tx.Delete(&role).Error; err != nil {
		tx.Rollback()
		return err
	}

	tx.Commit()
	return nil
}

func UpdateRole(id int, change *Role) error {
	role, err := GetRoleWithID(id)
	if err != nil {
		return err
	}

	tx := db.Begin()

	newRel := make(RoleMenuRelations, 0)
	delRel := make(RoleMenuRelations, 0)

	oldRoleMenusMap := role.RoleMenus.ToMap()
	newRoleMenusMap := change.RoleMenus.ToMap()

	for k, rel := range newRoleMenusMap {
		if _, ok := oldRoleMenusMap[k]; ok {
			delete(oldRoleMenusMap, k)
			continue
		}
		newRel = append(newRel, rel)
	}

	for _, rel := range oldRoleMenusMap {
		delRel = append(delRel, rel)
	}

	for _, rm := range newRel {
		rm.RoleID = role.ID
		if err := tx.Create(rm).Error; err != nil {
			tx.Rollback()
			return err
		}
	}

	for _, rm := range delRel {
		if err := tx.Delete(rm).Error; err != nil {
			tx.Rollback()
			return err
		}
	}

	if err := tx.Model(role).Updates(change).Error; err != nil {
		tx.Rollback()
		return err
	}

	tx.Commit()
	return nil
}
